<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>展示基因组 TR 拷贝数分布情况</title>
    <script src="d3.v6.min.js"></script>
    <script src="FileSaver.min.js"></script>
    <style>
        body {
            font-family: 'Times New Roman', sans-serif;
        }

        div {
            margin: 20px 0;
        }

        .tmp {
            display: none;
        }

        a.chr-link {
            display: inline-block;
            margin: 0 5px;
        }

        a.clicked {
            color: #f00;
        }

        .focus line {
            stroke: black;
            stroke-dasharray: 2, 2;
        }

        .overlay {
            fill: none;
            pointer-events: all;
        }

        .focus circle {
            fill: red;
            fill-opacity: 0.5;
        }

        .hidden {
            display: none;
        }

        #form {
            line-height: 2em;
        }

        .tip {
            position: absolute;
            display: none;
            background-color: #000;
            color: #fff;
            line-height: 1.2em;
            border: 1px solid #e1e3e2;
            padding: 10px;
            opacity: 0.8;
            font-size: 18px;
        }

        #img-copy-number {
            border: 1px dashed gray;
        }

        .comment {
            color: green;
            font-size: small;
        }
    </style>
</head>

<body>
    <h1>染色体序列中 TR 的拷贝数分布情况（每 Mb）</h1>
    <div id="form">
        <p class="params">
            <label for="p-svg_w">总宽度</label>
            <input type="number" name="p-svg_w" id="p-svg_w" min="100"> px
            <br>
            <label for="p-height">主图区占高</label>
            <input type="number" name="p-height" id="p-height" min="50"> px&emsp;
            <br>
            <label for="cnt-tab">选择 <i>process_trf_dat2get_best.pl</i> 生成的“.cnt.tab”文件：</label>
            <input type="file" name="cnt-tab" id="cnt-tab"><br>
            <span class="comment">选择文件后将自动绘图。</span>
        </p>
        <div class="img">
            <input type="button" id="save-svg" value="Save SVG">
        </div>
    </div>
    <div id="img-copy-number" class="img"></div>
    <div class="img comment">
        <ul>
            <li>鼠标移动到曲线上可查看相应位置各类 TR 具体拷贝数。</li>
            <li>点击图中靠上方的图例，可单独查看某大类 TR 拷贝数分布曲线。</li>
            <li>点击右上的“Refresh”，返回原图或更新图的尺寸。</li>
        </ul>
    </div>
    <div id="tooltip" class="tip"></div>
    <script>
        var ffamily = 'sans-serif';
        var fsize = {
            xlab: 10, ylab: 14, tickLable: 12
        };

        // 供设定的参数
        var settableParams = {
            svg_w: 900,
            height: 150,
            classes: []
        };
        setValues4form(settableParams);
        d3.selectAll('.img').classed('hidden', true);

        d3.select('#cnt-tab').on('change', function (event) {
            if (this.files.length === 0) {
                alert('请选择文件！');
                return;
            }
            const reader = new FileReader();
            reader.onload = function fileReadCompleted() {
                drawChr(parseCnt(reader.result), setParams());
                d3.selectAll('.img').classed('hidden', false);
            }
            reader.readAsText(this.files[0]);
        });

        function parseCnt(txt) {
            var lines = txt.split('\n');
            var colnames = lines.shift().split('\t');
            var data = [];
            lines.forEach(line => {
                var row = line.split('\t');
                if (row.length !== colnames.length) return;
                var dRow = {};
                for (var i = 0, n = colnames.length; i < n; ++i) {
                    dRow[colnames[i]] = +row[i];
                }
                data.push(dRow);
            });
            return data;
        }

        function drawChr(data, params) {
            var margin = { top: 40, right: 40, bottom: 50, left: 85 },
                height = params.height ? params.height : 120,
                hGap = 5, wGap = 5,
                svg_w = params.svg_w ? params.svg_w : 1200,
                svg_h = height + margin.top + margin.bottom,
                width = svg_w - margin.right - margin.left;

            // 清空
            d3.select('#img-copy-number').html('');

            var svg = d3.select('#img-copy-number').append('svg').attr('width', svg_w).attr('height', svg_h)
                .attr('xmlns', 'http://www.w3.org/2000/svg')
                .attr('version', '1.1'),
                g = svg.append('g').attr('class', 'container').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            // ==================================================
            var gChr = g.append('g').attr('class', 'chr')
                ;

            var classes = params.classes.length ? params.classes : Object.keys(data[0]);
            classes.shift();
            var xScale = d3.scaleLinear().range([0, width]).domain([0, data.length - 1]);
            var color = d3.scaleOrdinal().domain(classes)
                .range(params.colors ? params.colors : d3.schemeCategory10);

            var maxCnt = d3.max(
                data.map(
                    d => d3.max(classes.map(cls => d[cls]))
                )
            ) * 1.1, minCnt = d3.min(
                data.map(
                    d => d3.min(classes.map(cls => d[cls]))
                )
            ) * 0.7;

            var yScale = d3.scaleLinear().range([height, 0]).domain([
                minCnt, maxCnt
            ]);

            gChr.datum(data);
            classes.forEach(cls => {
                var linePath = d3.line()
                    .x(function (d) { return xScale(d.Mbp); })
                    .y(function (d) { return yScale(d[cls]); })
                    ;
                gChr.append('path')
                    .attr('class', 'num-of-' + cls)
                    .attr('d', function (d) { return linePath(d); })
                    .attr('fill', 'none')
                    .attr('stroke-width', 2)
                    .attr('stroke-opacity', 0.7)
                    .attr('stroke', color(cls))
                    ;
            });

            // x 轴
            gChr.append("g")
                .attr("class", "axis")
                .attr("transform", "translate(0," + (height + hGap * 2) + ")") // lenCCS1 : 10
                .call(d3.axisBottom(xScale))
                .attr('font-family', ffamily)
                .attr('font-size', fsize.tickLable + 'px')
                .append('text')
                .attr('x', width + wGap * 2).attr('y', 9)
                .attr('dy', 0.71 * fsize.xlab)
                .attr('font-size', fsize.xlab + 'px')
                .attr('text-anchor', 'start')
                .attr('fill', '#000')
                .text('Mb')
                ;

            // y 轴
            var xYlab = -50;
            var gYaxis = gChr.append("g")
                .attr("class", "axis")
                .attr("transform", "translate(" + (-wGap) + ",0)")
                .call(d3.axisLeft(yScale).ticks(6))
                .attr('font-size', fsize.tickLable + 'px')
                .attr('font-family', ffamily)
                ;
            gYaxis.selectAll('text').attr('dy', 0.32 * fsize.tickLable);

            gYaxis.append("text")
                .attr("x", xYlab)
                .attr("y", height / 2)
                .attr("fill", "#000")
                .attr("font-weight", "bold")
                .attr("text-anchor", "middle")
                .attr('font-size', fsize.ylab + 'px')
                .attr('transform', 'rotate(270 ' + xYlab + ',' + (height / 2) + ')')
                .text("TR Copy number")
                ;

            var legend = svg.append('g').attr('class', 'legend')
                .attr('transform', 'translate(100,' + margin.top / 2 + ')');
            var legend4classes = legend.append('g')
                .attr('stroke-width', 3)
                .attr('stroke-opacity', 0.7)
                .selectAll('g').data(classes).enter()
                .append('g')
                .attr("transform", function (d, i) { return "translate(" + i * 80 + ",0)"; })
                .style('cursor', 'pointer')
                ;
            legend4classes.append('line')
                .attr('x1', wGap).attr('y1', 0)
                .attr('x2', wGap + 30).attr('y2', 0)
                .attr('stroke', function (d) { return color(d); })
                ;
            legend4classes.append('text')
                .attr('class', 'legend-label')
                .attr('x', 0).attr('y', 0)
                .attr('dy', 0.32 * fsize.xlab)
                .attr('font-size', fsize.xlab + 'px')
                .attr('font-family', ffamily)
                .attr('text-anchor', 'end')
                .attr('fill', '#000')
                .text(function (d) { return d; })
                ;
            legend4classes.on('click', (event, d) => {
                drawChr(data, {
                    svg_w: settableParams.svg_w,
                    height: settableParams.height,
                    classes: ['Mbp', d],
                    colors: [color(d)]
                });
            });

            var refresh = svg.append('g')
                .attr('transform', 'translate(' + (svg_w - margin.right) + ',' + margin.top / 2 + ')')
                .style('cursor', 'pointer')
                ;
            refresh.append('text')
                .attr('x', 0).attr('y', 0)
                .attr('dy', 0.32 * fsize.xlab)
                .attr('font-size', fsize.xlab + 'px')
                .attr('font-family', ffamily)
                .attr('font-weight', 'bold')
                .attr('text-anchor', 'end')
                .attr('fill', '#f00')
                .text('Refresh')
                ;
            refresh.on('click', d => drawChr(data, setParams()));

            // --------------------------------------------
            var gFocus = gChr.append("g")
                .attr("class", "focus")
                .style("display", "none");

            // 添加一个透明的监视鼠标事件用的矩形
            gChr.append('rect')
                .attr("class", "overlay")
                .attr("x", 0).attr("y", 0)
                .attr("width", xScale(data.length))
                .attr("height", height)
                .on("mouseover", function () {
                    gFocus.style("display", null);
                })
                .on("mouseout", function () {
                    gFocus.style("display", "none");
                    d3.select('#tooltip').style('display', 'none');
                })
                .on("mousemove", mousemove);

            function mousemove(event) {
                gFocus.html(''); // 清空
                var d = data;

                var mouseX = d3.pointer(event)[0];

                var nMbp = parseInt(xScale.invert(mouseX));

                if (nMbp >= d.length) return;

                var focusX = xScale(nMbp);
                // 水平线
                gFocus.selectAll('.hLine').data(classes).enter()
                    .append('line').attr('class', 'hLine')
                    .attr('x1', -wGap).attr('y1', function (cls) { return yScale(d[nMbp][cls]); })
                    .attr('x2', focusX).attr('y2', function (cls) { return yScale(d[nMbp][cls]); });
                // 垂直线
                gFocus.append('line').attr('class', 'vLine')
                    .attr('x1', focusX).attr('y1', 0)
                    .attr('x2', focusX).attr('y2', height + hGap * 2);

                gFocus.selectAll('circle').data(classes).enter()
                    .append('circle')
                    .attr('r', 4)
                    .attr('cx', focusX)
                    .attr('cy', function (cls) { return yScale(d[nMbp][cls]); })
                    ;

                var txt = nMbp + '–' + (nMbp + 1) + ' Mb<br>';
                classes.forEach(cls => {
                    txt += cls + ': ' + d[nMbp][cls] + '<br>';
                });
                d3.select('#tooltip')
                    .style('display', 'block')
                    .style('position', 'fixed')
                    .style('top', (d3.pointer(event, svg)[1] - 25) + 'px')
                    .style('left', (d3.pointer(event, svg)[0] + 20) + 'px')
                    .html(txt);
            }
        }

        function dqS(id) {
            return document.querySelector('#' + id);
        }

        function setValues4form(params) {
            ['svg_w', 'height'].forEach(function (d) {
                dqS('p-' + d).value = params[d];
            });
        }

        function setParams() {
            ['svg_w', 'height'].forEach(function (d) {
                settableParams[d] = +dqS('p-' + d).value;
            });

            return settableParams;
        }
    </script>
    <!-- 保存图片 -->
    <div id="tmp-svg" class="tmp"></div>
    <script>
        d3.select('#save-svg').on('click', function () {
            // 去掉 focus
            var rawSVG = d3.select('#img-copy-number').html();
            d3.select('#tmp-svg').html(rawSVG);
            var tmpSVG = d3.select('#tmp-svg > svg');
            tmpSVG.selectAll('.focus').remove();
            tmpSVG.selectAll('.overlay').remove();

            var svgxml = d3.select('#tmp-svg').html();
            svgxml = '<?xml version="1.0" encoding="UTF-8" ?>' + "\n" + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' + svgxml;
            var blob = new Blob([svgxml], { type: 'image/svg+xml;charset=utf-8' });
            saveAs(blob, "tmp.svg");
            // 清空
            d3.select('#tmp-svg').html('');
        });
    </script>
</body>

</html>